package file_watcher

import (
	"fmt"
	"os"
	"path/filepath"
	"time"

	"engine/log"
	"gitee.com/ameise84/e3lock"
	"github.com/fsnotify/fsnotify"
)

type WatcherCallBack func(string, string)

func NewFileWatcher(dir string, callBack WatcherCallBack) (Watcher, error) {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		return nil, err
	}
	if err = watcher.Add(dir); err != nil {
		return nil, err
	}
	w := &fileWatcher{
		fileDir:   dir,
		fileNames: map[string]time.Time{},
		watcher:   watcher,
		callBack:  callBack,
	}
	return w, nil
}

type fileWatcher struct {
	fileDir   string
	fileNames map[string]time.Time
	watcher   *fsnotify.Watcher
	closeChan chan struct{}
	callBack  WatcherCallBack
	mu        e3lock.SpinLock
}

func (ts *fileWatcher) E3LogMarshall() string {
	return fmt.Sprintf("file watcher[%s]", ts.fileDir)
}

func (ts *fileWatcher) run() {
loopFor:
	for {
		select {
		case event, ok := <-ts.watcher.Events:
			if !ok {
				return
			}
			if event.Op&fsnotify.Write == fsnotify.Write {
				key := filepath.Base(event.Name)
				if _, ok = ts.fileNames[key]; ok {
					ts.readEvent(key)
				}
			}
		case err, ok := <-ts.watcher.Errors:
			if !ok {
				return
			}
			log.GetLogger().Error(err.Error()).Object(ts).Println()
		case <-ts.closeChan:
			break loopFor
		}
	}
}

func (ts *fileWatcher) readEvent(key string) {
	ts.mu.Lock()
	defer ts.mu.Unlock()
	filePath := filepath.Join(ts.fileDir, key)
	infoBefore, _ := os.Stat(filePath)
	before := infoBefore.ModTime()
	if ts.fileNames[key] == before {
		return
	}
	time.Sleep(500 * time.Millisecond)
	infoAfter, _ := os.Stat(filePath)
	if infoBefore.Size() == infoAfter.Size() && infoBefore.ModTime() == infoAfter.ModTime() {
		if ts.fileNames[key] != infoAfter.ModTime() {
			ts.callBack(ts.fileDir, key)
			ts.fileNames[key] = infoAfter.ModTime()
		}
	}
}

func (ts *fileWatcher) Watch(fileNames ...string) {
	ts.closeChan = make(chan struct{})
	for _, fileName := range fileNames {
		base := filepath.Base(fileName)
		info, _ := os.Stat(filepath.Join(ts.fileDir, base))
		if info == nil {
			ts.fileNames[base] = time.Now()
		} else {
			ts.fileNames[base] = info.ModTime()
		}
	}
	go ts.run()
}

func (ts *fileWatcher) Close() {
	close(ts.closeChan)
}
